package sadi.client;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Vector;
import javax.swing.JOptionPane;

import sadi.util.BJConstants;
import sadi.util.CardsCollection;

public class BJClient extends Thread implements BJConstants {

	private String userName;
	private DataInputStream fromServer;
	private DataOutputStream toServer;
	private ObjectInputStream in;
	private int status;
	private int initStatus;
	private int action;
	private int clientMove;
	private int deck1Score;
	private int deck2Score;
	private int maxHands, maxCards;
	private int deck;
	private int trialNum, gameNum;
	private int dealerCount;
	
	private Vector<Vector<String>> gameRankingCollection;	//variable to receive the game rank from server
	private Vector<Vector<String>> trialRankingCollection;	//variable to receive the trial rank from servr
	private String dealerFaceCard;
	private String dealerDeck[];
	private String clientCard[][];
	private CardsCollection cardsCollection;
	private GUI gui;
	private boolean wait;
	private boolean actionValidity;
	private boolean cont;
	private boolean splitted;
	
	public BJClient(GUI gui, String name)
	{
		userName = name;
		cardsCollection = new CardsCollection();
		this.gui = gui;
		
		trialNum = 0;
		gameNum = 0;
		
		dealerDeck = new String[MAX_CARDS];
		clientCard = new String[MAX_HANDS][MAX_CARDS];
	}
	
	/*
	 * call to connect to server 
	 */
	public void connectToServer()
	{
		boolean isConnected = false;
		try {
			
			//create a socket to connect to server
			Socket socket = new Socket("localhost", BJConstants.PORT);
			isConnected = true;
			
			//create an input stream to receive data from the server
			fromServer = new DataInputStream(socket.getInputStream());
			
			//create an output stream to send data to the server
			toServer = new DataOutputStream(socket.getOutputStream());
			toServer.writeUTF(userName);
			
			//create an object input stream to receive object from the server
			 in = new ObjectInputStream(fromServer);
			 
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			
			if(isConnected)
				JOptionPane.showMessageDialog(null, "Connection to server has been declined...", "Connection Declined", JOptionPane.INFORMATION_MESSAGE);
			else
				JOptionPane.showMessageDialog(null, "Remote host is not responding...\nPlease try again!", "Server Down", JOptionPane.INFORMATION_MESSAGE);
			
			System.exit(0);
		}
	}
	
	/*
	 * Inherited thread method
	 */
	public void run()
	{
		try 
		{
			//have to connect to server 1st
			connectToServer();
			initStatus = fromServer.readInt();
			
			while(true)
			{
				//the dealer card counter
				dealerCount = 0;
				
				//if the player join halfway during the game, he has to wait for the next game
				if(initStatus == NEWGAME_OBSERVE)
				{
					
					try 
					{
						if(trialNum == 0)
						{
							//reading dealer face card and ranking collection
							gameRankingCollection = (Vector<Vector<String>>) in.readObject();
							trialRankingCollection =  (Vector<Vector<String>>) in.readObject();
							dealerFaceCard = fromServer.readUTF();	//get the user face card 1st
							trialNum = fromServer.readInt() - 1; //adapt server's trial count of trial number to client's trial count of how many trial has passed
							
							gui.setStatus("Currently in observer mode.");
							gui.updateGameRank(gameRankingCollection);
							gui.updateTrialRank(trialRankingCollection);
						}
						else
							dealerFaceCard = fromServer.readUTF();
						
						
						dealerDeck[dealerCount] = dealerFaceCard;
						gui.updateCards(dealerFaceCard);
						gui.setDealerScore("--");		
						
					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
				}
				
				//if there is not enough player to start a game
				else if(initStatus == NEWGAME_WAIT)
				{
					gui.addClientButtonPanel();
					gui.setStatus("Waiting for more players.");
					status = fromServer.readInt(); //INGAME_WAIT status

					gui.setStatus("Please wait for your turn.");
					deck = 1;
					
					gui.deck1Turn();
					
					dealerFaceCard = fromServer.readUTF();
					clientCard[0][0] = fromServer.readUTF();
					clientCard[0][1] = fromServer.readUTF();
					dealerDeck[dealerCount] = dealerFaceCard;
					deck1Score = cardsCollection.countTotal(clientCard[0]);
					gui.updateCards(dealerFaceCard, clientCard[0][0], clientCard[0][1]); 	//will display the card to the client

					gui.setDealerScore("--");
					gui.setDeck1Score(deck1Score);
					maxHands = 0;
					maxCards = 2;
				
				
					splitted = false;
					cont = !cardsCollection.isBlackJack(clientCard[0]);
					
					if(!cont) //Player has blackjack from start
						gui.setStatus("Congratulation! You've a Blackjack!");
						
					while(cont)
					{
						status = fromServer.readInt(); //INGAME_MYTURN
						gui.setStatus("Please make your move within 15 seconds.");
						boolean hasResponse;
							do{
								enableButton(clientCard[deck-1], splitted); 	
							
								//if user doesn't respond, that means he withdraws from the try
								hasResponse = getUserRespond();
								if(hasResponse)
								{
									actionValidity = fromServer.readBoolean();
								}
								else
								{
									break;
								}
								
							}while(actionValidity == false);
					

						if((clientMove == ACT_HIT) && hasResponse)
						{
							String tempCard = fromServer.readUTF();
							gui.setStatus("Hit & drew " + tempCard);
							clientCard[maxHands][maxCards] = tempCard;

							//setting the cards total ont he correct deck
							if(deck == 1)
							{
								deck1Score = cardsCollection.countTotal(clientCard[maxHands]);
								gui.setDeck1Score(deck1Score);
							}
							else
							{
								deck2Score = cardsCollection.countTotal(clientCard[maxHands]);
								gui.setDeck2Score(deck2Score);
							}
							gui.updateClientCards(clientCard[maxHands][maxCards]);
							maxCards++;
						}
						else if ((clientMove == ACT_SPLIT) && hasResponse)
						{
							//move the splitted card to the 2nd row in the array
							gui.setStatus("Split cards");
							clientCard[1][0] = clientCard[0][1];
							clientCard[0][1] = null;
							maxCards--;
							
							gui.splitCard(clientCard[1][0]);
							deck1Score = cardsCollection.countTotal(clientCard[0]);	
							gui.setDeck1Score(deck1Score);
							deck2Score = cardsCollection.countTotal(clientCard[1]);
							gui.setDeck2Score(deck2Score);
							splitted = true;						
						}
						
						status = fromServer.readInt();
						if((status == INGAME_WITHDRAW) || (status == INGAME_BURST) || (status == INGAME_STAND))
						{
							String guiStatus = "";						
							if(status == INGAME_WITHDRAW)
								guiStatus = "Withdrawn due to time-out.";
							else if(status == INGAME_BURST)
								guiStatus = "Burst!";
							else //(status == INGAME_STAND)
								guiStatus = "Stand!";
							
							gui.setStatus(guiStatus);
							
							if((splitted == true) && maxHands == 0) //Switch to 2nd hand
							{
								maxHands++;
								maxCards = 1;
								cont = true;
								deck = 2;
								gui.setDeck2();
								gui.deck2Turn();
							}
							else
								cont = false;							
						}
						else if(status == INGAME_CONTINUE)
						{
							cont = true;
						}
				
					}
				}
				
				char key;
				gui.dealerTurn();	//will switch to dealer
				
				//reading the dealer card one by one. 
				//if the 1st char is not 'C', it means the dealer has stopped drawing card and has received
				//the dealer cards' total
				do
				{
				     String dealerAct = fromServer.readUTF();
				     
				      key = dealerAct.charAt(0);
				     String value = dealerAct.substring(1);

				     if(key == 'C')
				    	 gui.updateDealerCard(value);
				     else
				          gui.setDealerScore(value);
				     
				}while(key == 'C');

				
				String winner = fromServer.readUTF();
				int winnerCardValue = fromServer.readInt();
				gui.changeStatus(winner, winnerCardValue);
				
				//get the latest game rank and trial rank and update to the panel
				try {
					gameRankingCollection = (Vector<Vector<String>>) in.readObject();
					trialRankingCollection =  (Vector<Vector<String>>) in.readObject();
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				gui.updateGameRank(gameRankingCollection);
				gui.updateTrialRank(trialRankingCollection);

				try {
					sleep(1000);
					if(trialNum != (TRIAL_PERGAME - 1))
						//will pause the game 5 seconds in between trial
						for(int i=(BJConstants.PAUSE/1000); i>0; i--)
						{
							gui.setStatus("Pause for " + i + " second(s)...");
							sleep(1000);
						}
					else
					{
						//will pause the game for 5 seconds in between game
						//will pause the game 5 seconds in between trial
						for(int i=(BJConstants.PAUSE/1000); i>0; i--)
						{
							gui.setStatus("Pause for " + i + " second(s)... Starting a new game soon...");
							sleep(1000);
						}
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				gui.resetGame();	//clear the gui when the game ends
				trialNum++;
				
				deck1Score = 0;
				deck2Score = 0;

				//resetting the dealer and client card
				
				for(int i=0; i<MAX_CARDS; i++)
					dealerDeck[i] = null;
				
				for(int i=0; i<MAX_HANDS; i++)
				{
					for(int j=0; j<MAX_CARDS; j++)
						clientCard[i][j] = null;
				}
				
				if(trialNum == TRIAL_PERGAME)
				{
					gameNum++;
					trialNum = 0;
				    fromServer.readBoolean();
				    toServer.writeBoolean(true);
				    gui.resetTrialRank();
				    
				    if(initStatus == NEWGAME_OBSERVE)
				    	initStatus = fromServer.readInt();
				}
			}
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	/*
	 * a wait boolean variable is used to sleep the thread
	 * if it has awaken, it will awake.
	 */
	public void changeWaitStatus(int action)
	{
		wait = false;
		//save the action move
		clientMove = action;
	}
	
	/*
	 * enable the button only it is stanable or splittable
	 */
	public void enableButton(String deck[], boolean isSplitted)
	{
	
		boolean isSplitable = CardsCollection.isSplitable(deck);
		if((isSplitable == true) && !isSplitted)
		{
			gui.enableButton("SplitButton", true);
		}
		boolean isStandable = CardsCollection.isStandable(deck);
		if(isStandable == true)
		{
			gui.enableButton("StandButton", true);
		}
		
		gui.enableButton("HitButton", true);
	}
	
	/*
	 * will get user respond
	 * if user repond within 15 seconds, will return true
	 * else return false
	 */
	public boolean getUserRespond()
	{
		int totalDuration = 0, duration = 100;
		wait = true;
		
		while(wait && (totalDuration < BJConstants.TIMEOUT))
		{
			try {		
				//sleep the thread to wait for user respond
				sleep(duration);
				totalDuration += duration;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		//after client respond or 15 seconds have passed, will disable all the buttons
		gui.disableButton();
		
		if(totalDuration == BJConstants.TIMEOUT)
		{
			gui.setStatus("Time out.");
			return false;
		}
		else
		{
			try
			{
				//send the client move to the server
				toServer.writeInt(clientMove);
			}
			catch(IOException e)
			{
				e.printStackTrace();
			}
			
			return true;
		}		
	}
	
	//return if the current game is splitted
	public boolean isSplitted()
	{
		if(splitted)
			return true;
		else
			return false;
	}
	
	//reset the splitted to false
	public void resetSplitted()
	{
		splitted = false;
	}
}
